package com.axapoint.opoc.core.dao;

import java.util.Collection;
import java.util.LinkedList;
import java.util.UUID;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.NaturalId;
import org.joda.time.DateTime;
import org.joda.time.Interval;

import com.anasoft.os.daofusion.bitemporal.BitemporalWrapper;
import com.anasoft.os.daofusion.bitemporal.TimeUtils;
import com.anasoft.os.daofusion.bitemporal.WrappedBitemporalProperty;

@MappedSuperclass
public abstract class ObjectRegistration<E extends ObjectRegistration<E, V, B>, V extends ObjectRegistration._Value<E, B>, B extends ObjectRegistration._Bitemporal<B,V>> extends PersistentEntity {
	private static final long serialVersionUID = 1L;
	public static final int UUID_COLUMN_LENGTH = 36;
    public static final String UUID_COLUMN_NAME = "uuid";

	@MappedSuperclass
    public static abstract class _Value<E,B> extends PersistentEntity {
		private static final long serialVersionUID = 1L;

		// Reference to its bitemporal data
		@OneToOne(fetch = FetchType.LAZY)
	    @JoinColumn(name="id")
		private B bitemporal;
		
		@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
		@JoinColumn(name="ObjectId")
	    protected E entity;
		
		private String comment;

		// The operation types
		public enum Operation {CREATE, EDIT, PASSIVATE, IMPORT, DELETE};
		@Enumerated(EnumType.STRING)
		private Operation operation;

		// User performing the operation
		private String userId;

		// From system
		private String systemId;
		
		protected _Value() {};
		_Value(E entity, String systemId, String userId, Operation operation, String comment) {
			super();
			this.operation = operation;
			this.comment = comment;
			this.userId = userId;
			this.systemId = systemId;
			this.entity = entity;
		}
		public B getBitemporal() {
			return (B)bitemporal;
		}
		protected void setBitemporal(B bitemporal) {
			this.bitemporal = bitemporal;
		}
		public E getEntity() {
			return entity;
		}
		protected void setEntity(E entity) {
			this.entity = entity;
		}
		public Operation getOperation() {
			return operation;
		}
		protected void setOperation(Operation operation) {
			this.operation = operation;
		}
		public String getComment() {
			return comment;
		}
		protected void setComment(String comment) {
			this.comment = comment;
		}
		public String getUserId() {
			return userId;
		}
		protected void setUserId(String userId) {
			this.userId = userId;
		}
		public String getSystemId() {
			return systemId;
		}
		protected void setSystemId(String systemId) {
			this.systemId = systemId;
		}
		protected abstract String diff(_Value<E,B> otherValue, DateTime t0, DateTime t1);
		
		@Override
		public String toString() {
			StringBuffer sb = new StringBuffer();

			sb.append("  userId=").append(userId).append('\n');
			sb.append("  systemId=").append(systemId).append('\n');
			sb.append("  operation=").append(operation).append('\n');
			sb.append("  comment=").append(comment).append('\n');
			
			return sb.toString();
		}
	}

    @MappedSuperclass
    public abstract static class _Bitemporal<B extends _Bitemporal<B, V>, V extends _Value> extends BitemporalWrapper<V> {
		private static final long serialVersionUID = 1L;
		
		@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
	    protected V value;

		protected _Bitemporal() {}
	    public _Bitemporal(V value, Interval validityInterval) {
	        super(value, validityInterval);
	        value.bitemporal = (B)this;
	    }
	    @Override
	    public V getValue() {
	        return value;
	    }
	    @Override
	    protected void setValue(V value) {
	        this.value = value;
	    }
	    @Override
	    public String toString() {
	    	StringBuffer sb = new StringBuffer();

	    	sb.append("  Valid interval:  ").append(formatInterval(getValidityInterval())).append('\n');
	    	sb.append("  Record interval: ").append(formatInterval(getRecordInterval())).append('\n');
	    	sb.append(this.getValue().toString());
	    	
	    	return sb.toString();
	    }
		
		public abstract _Bitemporal<B,V> copyWith(Interval validityInterval);
	}

	@NaturalId
    @Column(length = UUID_COLUMN_LENGTH, name = UUID_COLUMN_NAME,
        unique = true, updatable = false, nullable = false)
    private String uuid;

	@SuppressWarnings("deprecation")
	@OneToMany(fetch = FetchType.LAZY)
    @Cascade(value = { org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
    @JoinColumn(name="ObjectId")
    protected Collection<B> values = new LinkedList<B>();	
		
	protected ObjectRegistration() {
		uuid = generateUuid();
	}
    public abstract WrappedBitemporalProperty<V, B> getValues();
	
    public String getUuid() {
        return uuid;
    }
    protected void setUuid(String uuid) {
        this.uuid = uuid;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
        return result;
    }

    @SuppressWarnings("unchecked")
	@Override
    public final boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof ObjectRegistration<?,?,?>)) {
            return false;
        }

        ObjectRegistration other = (ObjectRegistration<E,V,B>) obj;

        return (uuid == null) ? false : uuid.equals(other.uuid);
    }
    @SuppressWarnings("unchecked")
	@Override
    protected Object clone() throws CloneNotSupportedException {
    	ObjectRegistration clone = ObjectRegistration.class.cast(super.clone());
        clone.uuid = generateUuid();
        return clone;
    }
    protected String generateUuid() {
        return UUID.randomUUID().toString();
    }
	@Override
	public String toString() {
		StringBuffer sb = new StringBuffer();
		
		sb.append(this.getClass().getSimpleName()).append(" uuid=").append(uuid).append('\n');
		for (_Bitemporal bi : getValues().getData()) {
			sb.append(bi.toString()).append('\n');
		}
		
		return sb.toString();
	}
	
	public static String formatInterval(Interval interval) {
		StringBuffer sb = new StringBuffer();
		
		sb.append(interval.getStart().getDayOfMonth()).append("/");
		sb.append(interval.getStart().getMonthOfYear()).append("/");
		sb.append(interval.getStart().getYear()).append(" - ");

		if (interval.getEndMillis() == TimeUtils.ACTUAL_END_OF_TIME) {
			sb.append("INFINITY");
		} else {
			sb.append(interval.getEnd().getDayOfMonth()).append("/");
			sb.append(interval.getEnd().getMonthOfYear()).append("/");
			sb.append(interval.getEnd().getYear());
		}
		
		return sb.toString();
	}
}
